package cn.tedu.charging.order.service.impl;

import cn.tedu.charging.common.pojo.entity.ChargingProgressEsPO;
import cn.tedu.charging.common.pojo.message.ChargingCheckResultMessage;
import cn.tedu.charging.common.pojo.message.ChargingProgressMsg;
import cn.tedu.charging.common.pojo.message.DelayCheckMessage;
import cn.tedu.charging.common.pojo.message.ProgressData;
import cn.tedu.charging.common.pojo.param.ProgressCostParam;
import cn.tedu.charging.common.pojo.po.ChargingBillFailPO;
import cn.tedu.charging.common.pojo.po.ChargingBillSuccessPO;
import cn.tedu.charging.common.pojo.vo.ProgressCostVO;
import cn.tedu.charging.common.protocol.JsonResult;
import cn.tedu.charging.common.protocol.WebSocketMessage;
import cn.tedu.charging.common.utils.SnowflakeIdGenerator;
import cn.tedu.charging.order.client.CostClient;
import cn.tedu.charging.order.points.WsServerPoint;
import cn.tedu.charging.order.repository.BillRepository;
import cn.tedu.charging.order.repository.ChargingProgressEsRepository;
import cn.tedu.charging.order.service.ConsumerService;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.net.http.WebSocket;
import java.time.LocalDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;


@Service
@Slf4j
public class ConsumerServiceImpl implements ConsumerService {
    @Autowired
    private BillRepository billRepository;
    @Autowired
    private WsServerPoint wsServerPoint;
    @Autowired
    private CostClient costClient;
    @Autowired
    private ChargingProgressEsRepository chargingProgressEsRepository;
    private SnowflakeIdGenerator generator=new SnowflakeIdGenerator();
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public void handleCheckResult(ChargingCheckResultMessage message) {
        Boolean exists = checkBillExists(message.getOrderNo());
        if (!exists){
            //1.读取result
            Boolean result = message.getResult();
            //判断自检结果
            if (result){
                log.debug("设备自检结果成功,billId:{},userId:{},gunId:{}",message.getOrderNo(),message.getUserId(),message.getGunId());
                handleBillSuccess(message);
            }else{
                log.debug("设备自检结果失败,billId:{},userId:{},gunId:{}",message.getOrderNo(),message.getUserId(),message.getGunId());
                handleBillFail(message);
            }
        }
    }

    @Override
    public void handleDelayCheck(DelayCheckMessage message) {
        //1.组织一个查询 查询是否存在success或者fail
        Boolean exists=checkBillExists(message.getOrderNo());
        //2.判断不存在
        if (!exists){
            //按照失败处理
            //3.存储失败订单
            saveFailBill("设备无响应",message.getOrderNo(),message.getUserId(),message.getGunId(),new Date());
            // 4.通知用户换枪
            WebSocketMessage webSocketMessage = new WebSocketMessage();
            webSocketMessage.setMessage("设备无响应");
            webSocketMessage.setState(1);
            webSocketMessage.setData("对不起设备自检失败,您就等了,送您一张优惠券" +
                    "http://U78ks4hs");
            sendFailMessage2User(message.getUserId(),webSocketMessage);
            //TODO 5.通知设备 设备关账
            updateGunStatus(message.getGunId());
        }
    }

    /**
     * 设备推送同步的充电进度(在过去的20秒钟,用户充电度数,用户身份,订单信息,设备信息)
     * @param msg
     */
    @Override
    public void handleChargingProgress(ChargingProgressMsg msg) {
        //1.检查设备状态,有异常直接结束-轻做
        boolean gunOk=checkGunStatus(msg.getGunId());
        //2.计算当前充电的金额 capacity主要计算条件 gunId pileId stationId
        ProgressCostVO costVO=calculateCost(msg);
        //3.查询用户余额
        Double balance=getUserBalancer(msg.getUserId());
        //4.组织充电进度海量数据po持久化对象写入到es
        persistProgressPO(msg,costVO.getTotalCost());
        //5.推送消息 2大类 一大类 字符串小信息(gunOk,balance,msg.getIsFull)
        if (msg.getIsFull()){
            //组织消息 通知用户充电已满
            WebSocketMessage<String> isFullMessage=new WebSocketMessage<>();
            isFullMessage.setState(1);
            isFullMessage.setMessage("充满了");
            isFullMessage.setData("已经充满了,请走人,结账");
            wsServerPoint.sendMsg2User(msg.getUserId(),JSON.toJSONString(isFullMessage));
        }else{
            if (!gunOk){
                //组织消息 通知用户充电已满
                WebSocketMessage<String> message=new WebSocketMessage<>();
                message.setState(1);
                message.setMessage("枪有问题");
                message.setData("枪状态不正常,请拔枪结账走人");
                wsServerPoint.sendMsg2User(msg.getUserId(),JSON.toJSONString(message));
            }else if (balance<costVO.getTotalCost()){
                //组织消息 通知用户充电已满
                WebSocketMessage<String> message=new WebSocketMessage<>();
                message.setState(2);
                message.setMessage("余额不足");
                message.setData("您的余额不足,请充值结账");
                wsServerPoint.sendMsg2User(msg.getUserId(),JSON.toJSONString(message));
            }else{
                //正常充电进度,组织消息对象使用wsServerPoint推送 让小程序展示数据面板
                WebSocketMessage<ProgressData> message=new WebSocketMessage<>();
                message.setState(3);//1 输出提示窗口字符串 异常 错误 完成 2 余额不足 3充电季度
                message.setMessage("ok");
                //封装ProgressData
                ProgressData progressData=new ProgressData();

                progressData.setChargingCapacity(caculateCapacity(msg));//同步的是总度数,计算单次度数
                progressData.setTotalCapacity(msg.getChargingCapacity());//消息携带的是每次总充电度数
                progressData.setTotalCost(costVO.getTotalCost());
                progressData.setOneElectricityCost(costVO.getPowerFee());
                //三种api可以获取 当前系统的小时数 分钟数 秒数 Date Calander LocaldataTime
                //int hours=new Date().getHours();
                //int hours= Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
                int hour = LocalDateTime.now().getHour();
                int minute = LocalDateTime.now().getMinute();
                int second = LocalDateTime.now().getSecond();
                progressData.setHours(hour);
                progressData.setMinutes(minute);
                progressData.setSeconds(second);
                message.setData(progressData);
                wsServerPoint.sendMsg2User(msg.getUserId(),JSON.toJSONString(message));
            }
        }
    }

    private Double caculateCapacity(ChargingProgressMsg msg) {
        //1.准备一个和订单有关的key值 读取上次存储的总度数
        String lastOrderTotalKey="charging:order:last:cap:"+msg.getOrderNo();
        ValueOperations<String,Double> stringOps = redisTemplate.opsForValue();
        Double lastTotalCap = stringOps.get(lastOrderTotalKey);
        //准备返回值
        Double capacity=null;
        if (lastTotalCap!=null){
            log.debug("本次读到的不是第一次同步");
            capacity=msg.getChargingCapacity()-lastTotalCap;
        }else{
            log.debug("本次电量是第一次同步");
            capacity=msg.getChargingCapacity();
        }
        //3.将本次同步的总度数存储到redis供后续计算 这个计算的数据 知识短暂停留 12小时最大的存储时间上限
        stringOps.set(lastOrderTotalKey,msg.getChargingCapacity(),12, TimeUnit.HOURS);
        return capacity;
    }

    private void sendProgressMessage2User(ProgressCostVO costVO, ChargingProgressMsg msg) {

    }

    private void persistProgressPO(ChargingProgressMsg msg,Double totalCost) {
        //1.组织一个PO对象
        ChargingProgressEsPO esPo=new ChargingProgressEsPO();
        BeanUtils.copyProperties(msg,esPo);
        //1.1 补充缺少的数据
        esPo.setTotalCost(totalCost);
        //1.2 id值业务需要业务补充 必定要使用到时序编码 根据时间有大小判断的值 雪花算法
        esPo.setId(generator.nextId()+"");
        //2.调用数据层save
        chargingProgressEsRepository.save(esPo);
    }

    private Double getUserBalancer(Integer userId) {
        //轻做,利用userId 检查用户情况是否针对当前订单,需要先付费后充电 查询余额
        log.debug("获取用户余额,userId:{}",userId);
        return Double.valueOf("99999.99");
    }

    private ProgressCostVO calculateCost(ChargingProgressMsg msg) {
        //1.组织接口入参
        ProgressCostParam param=new ProgressCostParam();
        //msg消息完全可以覆盖 计价入参
        BeanUtils.copyProperties(msg,param);
        //2.调用costClient的接口
        JsonResult<ProgressCostVO> result = costClient.calculateCost(param);
        //3.解析计价结果
        return result.getData();

    }

    private boolean checkGunStatus(Integer gunId) {
        log.debug("查询设备服务,当前枪gunId:{},状态允许继续充电",gunId);
        return true;
    }

    private Boolean checkBillExists(String orderNo) {
        return billRepository.checkSuccessOrFail(orderNo);
    }

    private void handleBillFail(ChargingCheckResultMessage message) {
        //1.组织po 写入数据库
        saveFailBill(message.getFailDesc(),message.getOrderNo(),message.getUserId(),message.getGunId(),new Date());
        //2.远程调用修改枪状态 为故障 别人在查看设备情况的时候,不会因为这把枪空闲 而导航过来
        updateGunStatus(message.getGunId());
        //3.通知用户 道歉 换枪 优惠券连接
        WebSocketMessage webSocketMessage = new WebSocketMessage();
        webSocketMessage.setMessage(message.getFailDesc());
        webSocketMessage.setState(1);
        webSocketMessage.setData("设备启动失败,订单无法充电请换枪 ,sorry,给您一张优惠券" +
                "http://Tf3Dfo9");
        sendFailMessage2User(message.getUserId(),webSocketMessage);
    }

    private void sendFailMessage2User(Integer userId,WebSocketMessage message) {
        //2.转化成json
        String json= JSON.toJSONString(message);
        //3.websocket客户端将这个消息数据推给用户
        wsServerPoint.sendMsg2User(userId,json);
    }

    private void updateGunStatus(Integer gunId) {
        log.debug("通知设备 修改gunId:{},状态为故障status:{}",gunId,2);
    }

    private void saveFailBill(String failDesc,String billId,Integer userId,Integer gunId,Date now ) {
        //1.初始化赋值BillFailPO
        ChargingBillFailPO po=new ChargingBillFailPO();
        po.setFailDesc(failDesc);
        po.setBillId(billId);
        po.setUserId(userId);
        po.setGunId(gunId);
        po.setCreateTime(now);
        po.setUpdateTime(now);
        po.setDeleted(0);
        //2.数据层写入数据库
        billRepository.saveFailBill(po);
    }

    private void handleBillSuccess(ChargingCheckResultMessage message) {
        //1.存储成功订单
        saveSuccessBill(message);
        //2.通知设备可以开始充电了
        sendStartCharging(message);
        //3.给用户发送消息 通知充电马上开始
        sendStartCharging2User(message);
    }

    private void sendStartCharging2User(ChargingCheckResultMessage message) {
        //没有推送消息的客户端
        //1.封装一个即将推送出去的对象
        WebSocketMessage webSocketMessage=new WebSocketMessage();
        webSocketMessage.setState(1);
        webSocketMessage.setMessage("ok");
        webSocketMessage.setData("您的订单马上开始充电");
        //2.转化成json
        String json= JSON.toJSONString(webSocketMessage);
        //3.websocket客户端将这个消息数据推给用户
        wsServerPoint.sendMsg2User(message.getGunId(),json);
    }

    private void sendStartCharging(ChargingCheckResultMessage message) {
        log.debug("给设备发送命令,开始充电,pileId:{}",message.getPileId());
    }

    private void saveSuccessBill(ChargingCheckResultMessage message) {
        //1.组织一个success
        ChargingBillSuccessPO po=new ChargingBillSuccessPO();
        po.setBillId(message.getOrderNo());
        po.setUserId(message.getUserId());
        po.setBillStatus(1);//1充电中 2充电结束 3充电异常 4欠费 ..
        po.setChargingStartTime(new Date());
        po.setCreateTime(new Date());
        po.setUpdateTime(new Date());
        po.setDeleted(0);//逻辑删除的状态字段 1删掉了 0没删
        po.setGunId(message.getGunId());
        //po.setStationId();//远程查询调用设备 补充属性
        //po.setOperatorId();//远程调用运营商 补充
        //2.使用仓储层 写入到数据库
        billRepository.saveSuccessBill(po);
    }
}
